package com.vshop.framework.web.log;

import cn.hutool.core.io.IoUtil;
import cn.hutool.core.map.MapUtil;
import cn.hutool.core.util.ClassUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.ttl.threadpool.agent.internal.javassist.ClassPool;
import com.alibaba.ttl.threadpool.agent.internal.javassist.CtClass;
import com.alibaba.ttl.threadpool.agent.internal.javassist.CtMethod;
import com.alibaba.ttl.threadpool.agent.internal.javassist.NotFoundException;
import com.vshop.framework.core.util.ServletUtil;
import com.vshop.framework.web.repeatedly.RepeatedlyRequestWrapper;
import jakarta.servlet.http.HttpServletRequest;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.Ordered;

import org.springframework.web.servlet.ModelAndView;

import java.io.BufferedReader;
import java.io.IOException;
import java.lang.reflect.Method;
import java.util.Enumeration;
import java.util.Map;
import java.util.StringJoiner;

@Slf4j
@Aspect
public class WebLogTimeAspect implements Ordered {

    private final String prodProfile = "prod";
    private static int maxOutputLengthOfParaValue = 512;

    @Pointcut("@annotation(org.springframework.web.bind.annotation.GetMapping) || " +
            "@annotation(org.springframework.web.bind.annotation.PostMapping)")
    public void webLog() {
    }

    @Around("webLog()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {

        HttpServletRequest request = ServletUtil.getRequest();

        MethodSignature signature = (MethodSignature) proceedingJoinPoint.getSignature();
        Class<?> controllerClass = signature.getDeclaringType();
        Method method = signature.getMethod();
        int lineNumber = getLineNumber(controllerClass, method);

        String url = request.getRequestURL().toString();
//        String param = getRequestParamsString(request);
        String param = getRequestParams(request);
        String body = getRequestBody(request);

        long startTime = System.currentTimeMillis();
        Object result = null;
        try {
            result = proceedingJoinPoint.proceed();
        } finally {
            StringBuilder logInfo = new StringBuilder();
            logInfo.append("\n");
            logInfo.append("+========================================= Start ==========================================\n");
            logInfo.append("| Request        : ").append(request.getMethod()).append(" ").append(url).append("\n");
            logInfo.append("| Request Params : ").append(param).append("\n");
            logInfo.append("| Request Body   : ").append(body).append("\n");
            logInfo.append("| Request IP     : ").append(request.getRemoteAddr()).append("\n");
            logInfo.append("| Controller     : ").append(signature.getDeclaringTypeName()).append(".").append("(").append(controllerClass.getSimpleName()).append(".java:").append(lineNumber).append(")").append("\n");
            logInfo.append("| Method         : ").append(method.getName()).append(buildParamsString(method)).append("\n");
            logInfo.append("| Response       : ").append(getResponseText(result)).append("\n");
            logInfo.append("| Elapsed Time   : ").append(System.currentTimeMillis() - startTime).append(" ms").append("\n");
            logInfo.append("+========================================== End ===========================================\n");
//            System.out.println(logInfo);
            log.info(logInfo.toString());
        }
        return result;
    }

    private static String getResponseText(Object result) {
        if (result instanceof ModelAndView && ((ModelAndView) result).isReference()) {
            return ((ModelAndView) result).getViewName();
        }

        String originalText;
        if (result instanceof String) {
            originalText = (String) result;
        } else {
//            originalText = JSON.toJSONString(result);
            originalText = JSONUtil.toJsonStr(result);
        }

        if (StrUtil.isBlank(originalText)) {
            return "";
        }

        originalText = originalText.replace("\n", "");
        if (originalText.length() > 100) {
            return originalText.substring(0, 100) + ".....";
        }
        return originalText;
    }


    private String buildParamsString(Method method) {
        StringJoiner joiner = new StringJoiner(", ", "(", ")");
        for (Class<?> parameterType : method.getParameterTypes()) {
            joiner.add(parameterType.getSimpleName());
        }
        return joiner.toString();
    }


    private int getLineNumber(Class<?> controllerClass, Method method) throws NotFoundException {
//        CtClass ctClass = ClassPool.getDefault().get(ClassUtil.getUsefulClass(controllerClass).getName());
//        ClassPool.getDefault().get(ClassUtil.getUsefulClass(controllerClass).getName());
        CtClass ctClass = ClassPool.getDefault().get(ClassUtil.getClass(controllerClass).getName());
        ClassPool.getDefault().get(ClassUtil.getClass(controllerClass).getName());
        String desc = WebLogUtil.getMethodDescWithoutName(method);
        CtMethod ctMethod = ctClass.getMethod(method.getName(), desc);
        return ctMethod.getMethodInfo().getLineNumber(0);
    }


    private String getRequestParamsString(HttpServletRequest request) {
        StringBuilder sb = new StringBuilder();
        Enumeration<String> e = request.getParameterNames();
        if (e.hasMoreElements()) {
            while (e.hasMoreElements()) {
                String name = e.nextElement();
                String[] values = request.getParameterValues(name);
                if (values.length == 1) {
                    sb.append(name).append("=");
                    if (values[0] != null && values[0].length() > maxOutputLengthOfParaValue) {
                        sb.append(values[0], 0, maxOutputLengthOfParaValue).append("...");
                    } else {
                        sb.append(values[0]);
                    }
                } else {
                    sb.append(name).append("[]={");
                    for (int i = 0; i < values.length; i++) {
                        if (i > 0) {
                            sb.append(",");
                        }
                        sb.append(values[i]);
                    }
                    sb.append("}");
                }
                sb.append("  ");
            }
        }
        return sb.toString();
    }

    private String getRequestParams(HttpServletRequest request) throws IOException {

        String parameters = "";
        Map<String, String[]> parameterMap = request.getParameterMap();
        if (MapUtil.isNotEmpty(parameterMap)) {
//            parameters = JsonUtil.toJsonString(parameterMap);
            parameters = JSONUtil.toJsonStr(parameterMap);
        }
        return parameters;
    }

    private String getRequestBody(HttpServletRequest request) throws IOException {
        String jsonParam = "";
        if (ServletUtil.isJsonRequest(request)) {
            if (request instanceof RepeatedlyRequestWrapper) {
                BufferedReader reader = request.getReader();
                jsonParam = IoUtil.read(reader);
            }
        }
        return jsonParam;
    }

    @Override
    public int getOrder() {
        return Integer.MIN_VALUE;
    }
}
